﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpShare.Storage;
using SharpShare.Storage.Security;
using System.Collections.Concurrent;

namespace SharpShare.Afp.Protocol.Handlers {
    internal class AfpEnumerateExt2RequestHandler : IAfpRequestHandler {
        [Flags]
        private enum StorageItemType {
            Invalid = 0,
            File = 2,
            Container = 4,
            All = (File | Container)
        }

        private static readonly TimeSpan EnumerationContextExpiration = TimeSpan.FromMinutes(1.0);

        #region IAfpRequestHandler Members

        public byte CommandCode {
            get { return 68; }
        }

        public AfpResultCode Process(IAfpSession session, DsiHeader dsiHeader, AfpStream requestStream, AfpStream responseStream) {
            requestStream.ReadPadding();

            ushort volumeId = requestStream.ReadUInt16();
            uint directoryId = requestStream.ReadUInt32();
            AfpFileDirectoryBitmap fileBitmap = requestStream.ReadEnum<AfpFileDirectoryBitmap>();
            AfpFileDirectoryBitmap directoryBitmap = requestStream.ReadEnum<AfpFileDirectoryBitmap>();

            short reqCount = requestStream.ReadInt16();
            int startIndex = requestStream.ReadInt32();
            int maxReplySize = requestStream.ReadInt32();

            IAfpVolume volume;
            IStorageContainer lookAtContainer = requestStream
                .ReadPathAndFind(session, volumeId, directoryId, out volume)
                .AsContainer();

            if (lookAtContainer == null) {
                return AfpResultCode.FPParamErr;
            }

            lookAtContainer.GetEffectiveAccess(session).DemandRight(StorageAccessRight.ListContents);

            StorageItemType itemType = StorageItemType.All;

            if (fileBitmap == 0) {
                itemType ^= StorageItemType.File;
            }
            if (directoryBitmap == 0) {
                itemType ^= StorageItemType.Container;
            }

            EnumerationContext context = EnumerationContext.Acquire(session, lookAtContainer, itemType, startIndex - 1);

            IList<IStorageItem> contents;

            try {
                contents = context.Take(reqCount);
            } finally {
                context.Release();
            }

            if (contents.Count == 0) {
                return AfpResultCode.FPObjectNotFound;
            }

            AfpResultCode resultCode = contents.WriteStorageItems(false, volume, fileBitmap, directoryBitmap, responseStream);

            return resultCode;
        }

        #endregion

        private sealed class EnumerationContext {
            private IEnumerator<IStorageItem> _contentsEnumerator;
            private int _lastStartIndex;
            private bool _finished;

            private EnumerationContext(IAfpSession session, IStorageContainer container, StorageItemType type) {
                this.Session = session;
                this.Container = container;
                this.Type = type;
                _contentsEnumerator = container.Contents.GetEnumerator();
            }

            public IAfpSession Session { get; private set; }
            public IStorageContainer Container { get; private set; }
            public StorageItemType Type { get; private set; }

            public IList<IStorageItem> Take(int count) {
                var taken = this.PickUp().Take(count).ToList();
                _lastStartIndex += taken.Count;
                return taken;
            }

            private IEnumerable<IStorageItem> PickUp() {
                while (_contentsEnumerator.MoveNext()) {
                    IStorageItem current = _contentsEnumerator.Current;
                    if (this.Type == StorageItemType.Container) {
                        if (!current.Is<IStorageContainer>()) {
                            continue;
                        }
                    } else if (this.Type == StorageItemType.File) {
                        if (!current.Is<IStorageFile>()) {
                            continue;
                        }
                    }

                    yield return current;
                }

                _finished = true;
            }

            public void Release() {
                if (_finished && _lastStartIndex == 0)
                    return;

                AfpCache<Tuple<IStorageContainer, StorageItemType, int>, EnumerationContext> cache = this.Session.Properties.GetOrSet<AfpCache<Tuple<IStorageContainer, StorageItemType, int>, EnumerationContext>>();

                lock (cache) {
                    Tuple<IStorageContainer, StorageItemType, int> key = Tuple.Create(this.Container.DemandContainer(), this.Type, _lastStartIndex);

                    cache.Add(key, this, EnumerationContextExpiration);
                }
            }

            public static EnumerationContext Acquire(IAfpSession session, IStorageContainer container, StorageItemType type, int startIndex) {
                AfpCache<Tuple<IStorageContainer, StorageItemType, int>, EnumerationContext> cache = session.Properties.GetOrSet<AfpCache<Tuple<IStorageContainer, StorageItemType, int>, EnumerationContext>>();

                lock (cache) {
                    Tuple<IStorageContainer, StorageItemType, int> key = Tuple.Create(container, type, startIndex);

                    EnumerationContext context;
                    if (
                        cache.TryRemove(key, out context)) {
                        return context;
                    }

                    context = new EnumerationContext(session, container, type);
                    context.Take(startIndex);

                    return context;
                }
            }
        }
    }
}
